package com.hzgj.soa.spring;

import com.hzgj.bcl.concurrent.FutureListener;
import com.hzgj.bcl.soa.api.ProxyFactory;
import com.hzgj.bcl.soa.config.ReferenceDef;
import com.hzgj.bcl.soa.rpc.RpcDispatcher;
import com.hzgj.bcl.soa.rpc.retry.RetryTimes;
import com.hzgj.bcl.soa.util.Constants;
import com.hzgj.bcl.soa.util.NetUtils;
import com.hzgj.bcl.util.lang.StrKit;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.Map;

import static com.hzgj.bcl.soa.util.Constants.*;
import static com.hzgj.soa.spring.schema.SoaNamespaceHandler.DOMAINCONFIGBEAN_NAME;

public class ReferenceBean<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {

    @Getter
    @Setter
    private ReferenceDef referenceDef;
    @Getter
    @Setter
    private String id;
    private Class<?> interfaceClazz;
    private RpcDispatcher rpcDispatcher;
    private FutureListener futureListener;
    private ApplicationContext applicationContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        if (StrKit.notBlank(referenceDef.getListener())) {
            Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(referenceDef.getListener());
            if (!FutureListener.class.isAssignableFrom(clazz)) {
                throw new RuntimeException("reference listener " + referenceDef.getListener() + " should implement FutureListener");
            }
            futureListener = (FutureListener) clazz.newInstance();
        }
        fillReferenceConfig();
        fillConsume();
        if (referenceDef.getRetryPolicy() == null) {
            referenceDef.setRetryPolicy(new RetryTimes(referenceDef.getMsBeforeRetry(), referenceDef.getRetries()));
        }

        referenceDef.setHost(NetUtils.getAndCheckHost(referenceDef.getHost()));
        interfaceClazz = Thread.currentThread().getContextClassLoader().loadClass(referenceDef.getInterfaceClass());
        referenceDef.setMethodCount(interfaceClazz.getMethods().length);

        if (referenceDef.getMethodDefs() != null) {
            referenceDef.getMethodDefs().values().forEach(methodDef -> {
                if (methodDef.getIdempotentIdGenClass() != null) {
                    methodDef.setIdempotentIdGen(applicationContext.getBean(methodDef.getIdempotentIdGenClass()));
                }
                if (methodDef.getMsBeforeRetry() != DEFAULT_MSBEFORERETRY || methodDef.getRetries() != Constants.DEFAULT_RETRIES) {
                    methodDef.setRetryPolicy(new RetryTimes(methodDef.getMsBeforeRetry(), methodDef.getRetries()));
                } else {
                    methodDef.setRetryPolicy(referenceDef.getRetryPolicy());
                }
            });
        }
    }

    private void fillReferenceConfig() {
        ReferenceConfig referenceConfig = null;
        String domain = referenceDef.getDomain();
        DomainConfigBean domainConfigBean = applicationContext.getBean(DOMAINCONFIGBEAN_NAME, DomainConfigBean.class);

        if (StrKit.notBlank(domain)) {
            Domain domainConfig = domainConfigBean.getDomain(domain);
            referenceConfig = domainConfig.referenceConfig;
        }

        if (referenceConfig == null) {
            referenceConfig = domainConfigBean.getGlobalReferenceConfig();
            if (referenceConfig == null) {
                return;
            }
        }

        if (referenceDef.isCheck() == DEFAULT_CLUSTER_AVAILABLE_CHECK && referenceConfig.isCheck() != DEFAULT_CLUSTER_AVAILABLE_CHECK) {
            referenceDef.setCheck(referenceConfig.isCheck());
        }
        if (referenceDef.getConnectTimeoutInMills() == DEFAULT_CONNECTTIMEOUT && referenceConfig.getConnectTimeoutInMills() != DEFAULT_CONNECTTIMEOUT) {
            referenceDef.setConnectTimeoutInMills(referenceConfig.getConnectTimeoutInMills());
        }
        if (referenceDef.getRequestTimeoutInMills() == DEFAULT_REQUESTTIMEOUT && referenceConfig.getRequestTimeoutInMills() != DEFAULT_REQUESTTIMEOUT) {
            referenceDef.setRequestTimeoutInMills(referenceConfig.getRequestTimeoutInMills());
        }
        if (referenceDef.getCluster().equals(DEFAULT_CLUSTER_FAILOVER) && !referenceConfig.getCluster().equals(DEFAULT_CLUSTER_FAILOVER)) {
            referenceDef.setCluster(referenceConfig.getCluster());
        }
        if (referenceDef.getProtocol().equals(DEFAULT_PROTOCOL) && !referenceConfig.getProtocol().equals(DEFAULT_PROTOCOL)) {
            referenceDef.setProtocol(referenceConfig.getProtocol());
        }
        if (referenceDef.getLoadbalance().equals(DEFAULT_LOADBALANCE) && !referenceConfig.getLoadbalance().equals(DEFAULT_LOADBALANCE)) {
            referenceDef.setLoadbalance(referenceConfig.getLoadbalance());
        }
        if (referenceDef.getMsBeforeRetry() == DEFAULT_MSBEFORERETRY && referenceConfig.getMsBeforeRetry() != DEFAULT_MSBEFORERETRY) {
            referenceDef.setMsBeforeRetry(referenceConfig.getMsBeforeRetry());
        }
        if (referenceDef.getRetries() == DEFAULT_RETRIES && referenceConfig.getRetries() != DEFAULT_RETRIES) {
            referenceDef.setRetries(referenceConfig.getRetries());
        }
        if (referenceDef.isRegister() == DEFAULT_REGISTRY && referenceConfig.isRegister() != DEFAULT_REGISTRY) {
            referenceDef.setRegister(referenceConfig.isRegister());
        }
        if (StrKit.isBlank(referenceDef.getConsumer())) {
            referenceDef.setConsumer(referenceConfig.getConsumer());
        }
        if (StrKit.isBlank(referenceDef.getGroup())) {
            referenceDef.setGroup(referenceConfig.getGroup());
        }
        if (StrKit.isBlank(referenceDef.getVersion())) {
            referenceDef.setVersion(referenceConfig.getVersion());
        }
        if (StrKit.isBlank(referenceDef.getHost())) {
            referenceDef.setHost(referenceConfig.getHost());
        }
        if (StrKit.isBlank(referenceDef.getPool())) {
            referenceDef.setPool(referenceConfig.getPool());
        }
        if (StrKit.isBlank(referenceDef.getUrl())) {
            referenceDef.setUrl(referenceConfig.getUrl());
        }
    }

    private void fillConsume() {
        ConsumerConfig consumerConfig;
        if (StrKit.notBlank(referenceDef.getConsumer())) {
            Map<String, ConsumerConfig> customers = applicationContext.getBeansOfType(ConsumerConfig.class);
            consumerConfig = customers.get(referenceDef.getConsumer());
            if (consumerConfig == null) {
                throw new RuntimeException("can't find consumer config by " + referenceDef.getConsumer());
            }
            if (consumerConfig.getConnectTimeoutInMills() != DEFAULT_CONNECTTIMEOUT) {
                referenceDef.setConnectTimeoutInMills(consumerConfig.getConnectTimeoutInMills());
            }
            if (consumerConfig.getRequestTimeoutInMills() != DEFAULT_REQUESTTIMEOUT) {
                referenceDef.setRequestTimeoutInMills(consumerConfig.getRequestTimeoutInMills());
            }
            if (StrKit.notBlank(consumerConfig.getHost()) && StrKit.isBlank(referenceDef.getHost())) {
                referenceDef.setHost(consumerConfig.getHost());
            }
            if (consumerConfig.getMsBeforeRetry() != DEFAULT_MSBEFORERETRY || consumerConfig.getRetries() != DEFAULT_RETRIES) {
                referenceDef.setRetryPolicy(new RetryTimes(consumerConfig.getMsBeforeRetry(), consumerConfig.getRetries()));
            }
        }
    }

    @Override
    public void destroy() throws Exception {
        if (rpcDispatcher != null) {
            rpcDispatcher.stop();
        }
    }

    /**
     * @return
     * @throws Exception
     */
    @Override
    public Object getObject() throws Exception {
        rpcDispatcher = applicationContext.getBean(RpcDispatcher.class);
        return ProxyFactory.proxyBuilder(interfaceClazz).dispatch(rpcDispatcher).referenceDef(referenceDef)
                .futureListener(futureListener).build();
    }

    @Override
    public Class<?> getObjectType() {
        return interfaceClazz;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}